/******************************************************************************
 * Copyright 2020-2021 The Firmament Authors. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *****************************************************************************/
#include "device.h"
#include "f28p65x_device.h"

#include "drv_spi.h"
#include "hal/spi/spi.h"

// #define SPI_USE_DMA

struct __spi_bus_t {
    struct rt_spi_bus parent;
    uint32_t spi_periph;
    struct rt_spi_configuration bus_config;
#ifdef SPI_USE_DMA
    //TODO
#endif
};

struct __spi_gpio_cs {
    uint32_t pin_mux;
    uint32_t pin;
};

#define __SPI_INIT_GPIO_CONFIG(base, mosi, miso, clk)       \
    {                                                       \
        .spi_base   = base##_BASE,                          \
        .periph_clk = SYSCTL_PERIPH_CLK_##base,             \
        .pin_mosi   = mosi,                                 \
        .pin_miso   = miso,                                 \
        .pin_clk    = clk,                                  \
        .mux_mosi   = GPIO_##mosi##_##base##_PICO,          \
        .mux_miso   = GPIO_##miso##_##base##_POCI,          \
        .mux_clk    = GPIO_##clk##_##base##_CLK,            \
    }

typedef struct __spi_gpio_t {
    uint32_t spi_base;
    uint32_t periph_clk;
    uint32_t pin_mosi;
    uint32_t pin_miso;
    uint32_t pin_clk;
    uint32_t mux_mosi;
    uint32_t mux_miso;
    uint32_t mux_clk;
} spi_gpio_t;

static spi_gpio_t spi_bus_arr[] = {
    /*------------------ SPI_BASE  MOSI  MISO  CLK */
    __SPI_INIT_GPIO_CONFIG(SPIA,   218,   33,   34 ),
    __SPI_INIT_GPIO_CONFIG(SPIB,   60,    61,   58 ),
    __SPI_INIT_GPIO_CONFIG(SPIC,   69,    70,   71 ),
};

static void gpio_bit_set(uint32_t pin_mux, uint32_t pin)
{
    GPIO_writePin(pin, 1);
}

static void gpio_bit_reset(uint32_t pin_mux, uint32_t pin)
{
    GPIO_writePin(pin, 0);
}
/*----------------------------------------------------------------------*/
/**
 * @brief Configure spi device
 * 
 * @param device SPI device
 * @param configuration SPI device configuration
 * @return rt_err_t RT_EOK for success
 */
static rt_err_t configure(struct rt_spi_device* device,
                          struct rt_spi_configuration* configuration)
{
    struct __spi_bus_t* spi_bus = (struct __spi_bus_t*)device->bus;

    uint32_t spi_base = spi_bus->spi_periph;
    uint32_t spi_mode = 0;
    uint32_t data_width = 0;

    if (spi_bus->bus_config.mode == configuration->mode
        && spi_bus->bus_config.data_width == configuration->data_width
        && spi_bus->bus_config.max_hz == configuration->max_hz) {
        /* same configuration, do not need re-configure */
        return RT_EOK;
    }

    if (configuration->data_width <= 8) {
        data_width = 8;
    } else if (configuration->data_width <= 16) {
        data_width = 16;
    } else {
        rt_kprintf("spi data width %d not support\r\n", configuration->data_width);
        return RT_EIO;
    }

    /* CPOL */
    if (configuration->mode & RT_SPI_CPOL) { // CPOL = 1
        /* CPHA */
        if (configuration->mode & RT_SPI_CPHA) {
            spi_mode = SPI_PROT_POL0PHA0;       // CPOL = 1; CPHA = 1
        } else {
            spi_mode = SPI_PROT_POL1PHA0;       // CPOL = 1; CPHA = 0
        }
    } else { // CPOL = 0
        /* CPHA */
        if (configuration->mode & RT_SPI_CPHA) {
            spi_mode = SPI_PROT_POL0PHA1;       // CPOL = 0; CPHA = 1
        } else {
            spi_mode = SPI_PROT_POL1PHA1;       // CPOL = 0; CPHA = 0
        }
    }

    SPI_disableModule(spi_base);
    SPI_setConfig(spi_base, DEVICE_LSPCLK_FREQ, spi_mode, SPI_MODE_CONTROLLER, configuration->max_hz, data_width);
    // SPI_setFIFOInterruptLevel(spi_base, SPI_FIFO_TX1, SPI_FIFO_RX1);
    SPI_disableInterrupt(spi_base, SPI_INT_RXFF | SPI_INT_TXFF);
    SPI_enableModule(spi_base);

    /* update SPI bus configuration */
    spi_bus->bus_config = *configuration;

    return RT_EOK;
}

/**
 * @brief SPI transfer function
 * 
 * @param device SPI device instance
 * @param message SPI message to be transfered
 * @return rt_uint32_t bytes have been transfered
 */
static rt_uint32_t transfer(struct rt_spi_device* device, struct rt_spi_message* message)
{
    struct __spi_bus_t* spi_bus = (struct __spi_bus_t*)device->bus;
    struct __spi_gpio_cs* spi_gpio_cs = (struct __spi_gpio_cs*)device->parent.user_data;
    struct rt_spi_configuration* config = &device->config;

    uint32_t spi_base = spi_bus->spi_periph;
    rt_uint32_t size = message->length;
    uint32_t timeout;

    /* take CS */
    if (message->cs_take) {
        // rt_kprintf("CS(%d) take \r\n", spi_gpio_cs->pin);
        gpio_bit_reset(spi_gpio_cs->pin_mux, spi_gpio_cs->pin);
    }

    /*-------------------------------------*/
    {
        if (config->data_width <= 8) {
            const rt_uint8_t* send_ptr = message->send_buf;
            rt_uint8_t* recv_ptr = message->recv_buf;

            while (size--) {
                rt_uint8_t data = 0xFF;

                if (send_ptr != RT_NULL) {
                    data = *send_ptr++;
                }

                /* Wait until the transmit buffer is empty */
                timeout = 0xFFFF;
                while ((SPI_getTxFIFOStatus(spi_base) != SPI_FIFO_TXEMPTY) && (timeout--))
                    ;

                /* Send the byte */
                SPI_writeDataNonBlocking(spi_base, data);

                /* Wait until a data is received */
                timeout = 0xFFFF;
                while ((SPI_getRxFIFOStatus(spi_base) == SPI_FIFO_RXEMPTY) && (timeout--))
                    ;

                /* Get the received data */
                data = SPI_readDataNonBlocking(spi_base);

                if (recv_ptr != RT_NULL) {
                    *recv_ptr++ = data;
                }
            }
        } else if (config->data_width <= 16) {
            const rt_uint16_t* send_ptr = message->send_buf;
            rt_uint16_t* recv_ptr = message->recv_buf;

            while (size--) {
                rt_uint16_t data = 0xFFFF;

                if (send_ptr != RT_NULL) {
                    data = *send_ptr++;
                }

                /* Wait until the transmit buffer is empty */
                timeout = 0xFFFF;
                while ((SPI_getTxFIFOStatus(spi_base) != SPI_FIFO_TXEMPTY) && (timeout--))
                    ;

                /* Send the byte */
                SPI_writeDataNonBlocking(spi_base, data);

                /* Wait until a data is received */
                timeout = 0xFFFF;
                while ((SPI_getRxFIFOStatus(spi_base) == SPI_FIFO_RXEMPTY) && (timeout--))
                    ;

                /* Get the received data */
                data = SPI_readDataNonBlocking(spi_base);

                if (recv_ptr != RT_NULL) {
                    *recv_ptr++ = data;
                }
            }
        }
    }
    /*-------------------------------------*/

    /* release CS */
    if (message->cs_release) {
        gpio_bit_set(spi_gpio_cs->pin_mux, spi_gpio_cs->pin);
        // rt_kprintf("CS(%d) release \r\n", spi_gpio_cs->pin);
    }

    return message->length;
}

static struct rt_spi_ops qx_spi_ops = {
    configure,
    transfer
};

/** \brief init and register spi bus.
 *
 * \param SPI: SPI, e.g: SPIA,SPIB,SPIC.
 * \param stm32_spi: stm32 spi bus struct.
 * \param spi_bus_name: spi bus name, e.g: "spi1"
 * \return rt_err_t RT_EOK for success
 */
static rt_err_t spi_bus_register(uint32_t spi_periph,
                                  struct __spi_bus_t* spi_bus,
                                  const char* spi_bus_name)
{
    spi_gpio_t* pgpio = RT_NULL;

    if (SPIA_BASE == spi_periph) {
        pgpio = &spi_bus_arr[0];
        spi_bus->spi_periph = SPIA_BASE;

    } else if (SPIB_BASE == spi_periph) {
        pgpio = &spi_bus_arr[1];
        spi_bus->spi_periph = SPIB_BASE;

    } else if (SPIC_BASE == spi_periph) {
        pgpio = &spi_bus_arr[2];
        spi_bus->spi_periph = SPIC_BASE;

    } else {
        return RT_ENOSYS;
    }

    SysCtl_enablePeripheral(pgpio->periph_clk);

    GPIO_MuxConfig(pgpio->pin_clk,  pgpio->mux_clk,  GPIO_PIN_TYPE_STD, GPIO_QUAL_ASYNC);
    GPIO_MuxConfig(pgpio->pin_mosi, pgpio->mux_mosi, GPIO_PIN_TYPE_STD ,GPIO_QUAL_ASYNC);
    GPIO_MuxConfig(pgpio->pin_miso, pgpio->mux_miso, GPIO_PIN_TYPE_STD, GPIO_QUAL_ASYNC);

    rt_kprintf("SPI init [%s]: pin - %d %d %d \r\n", spi_bus_name, pgpio->pin_clk, pgpio->pin_mosi, pgpio->pin_miso);

    return rt_spi_bus_register(&spi_bus->parent, spi_bus_name, &qx_spi_ops);
}

/**
 * @brief Initialize spi bus and device
 * 
 * @return rt_err_t RT_EOK for success
 */
rt_err_t drv_spi_init(void)
{
    static struct __spi_bus_t bus_spi0;
    static struct __spi_bus_t bus_spi1;
    static struct __spi_bus_t bus_spi2;

#define __INIT_GPIO_CS(num)                  \
    {                                        \
        .pin_mux = GPIO_##num##_##GPIO##num, \
        .pin     = num,                      \
    }

    /*----------------------------------------------------------------------------------------*/
    /* register SPI0 bus */
    RT_TRY(spi_bus_register(SPIA_BASE, &bus_spi0, "spi0"));

    /* attach spi_device_0 (BMI088 ACCEL) to spi0 */
    {
        static struct rt_spi_device rt_spi0_dev0;
        static struct __spi_gpio_cs spi0_cs0 = __INIT_GPIO_CS(214);

        /* set CS pin by default */
        GPIO_MuxConfig(spi0_cs0.pin, spi0_cs0.pin_mux, GPIO_PIN_TYPE_STD, GPIO_QUAL_ASYNC);
        GPIO_setDirectionMode(spi0_cs0.pin, GPIO_DIR_MODE_OUT);
                gpio_bit_set(spi0_cs0.pin_mux, spi0_cs0.pin);

        RT_TRY(rt_spi_bus_attach_device(&rt_spi0_dev0, "spi0_dev0", "spi0", (void*)&spi0_cs0));
    }

    /* attach spi_device_1 (BMI088 GYRO) to spi0 */
    {
        static struct rt_spi_device rt_spi0_dev1;
        static struct __spi_gpio_cs spi0_cs1 = __INIT_GPIO_CS(215);

        /* set CS pin by default */
        GPIO_MuxConfig(spi0_cs1.pin, spi0_cs1.pin_mux, GPIO_PIN_TYPE_STD, GPIO_QUAL_ASYNC);
        GPIO_setDirectionMode(spi0_cs1.pin, GPIO_DIR_MODE_OUT);
        gpio_bit_set(spi0_cs1.pin_mux, spi0_cs1.pin);

        RT_TRY(rt_spi_bus_attach_device(&rt_spi0_dev1, "spi0_dev1", "spi0", (void*)&spi0_cs1));
    }

    /* attach spi_device_2 (BMM150 MAG) to spi0 */
    {
        static struct rt_spi_device rt_spi0_dev2;
        static struct __spi_gpio_cs spi0_cs2 = __INIT_GPIO_CS(217);

        /* set CS pin by default */
        GPIO_MuxConfig(spi0_cs2.pin, spi0_cs2.pin_mux, GPIO_PIN_TYPE_STD, GPIO_QUAL_ASYNC);
        GPIO_setDirectionMode(spi0_cs2.pin, GPIO_DIR_MODE_OUT);
        gpio_bit_set(spi0_cs2.pin_mux, spi0_cs2.pin);

        RT_TRY(rt_spi_bus_attach_device(&rt_spi0_dev2, "spi0_dev2", "spi0", (void*)&spi0_cs2));
    }

    /* attach spi_device_3 (SPL06 Baro) to spi0 */
    {
        static struct rt_spi_device rt_spi0_dev3;
        static struct __spi_gpio_cs spi0_cs3 = __INIT_GPIO_CS(216);

        /* set CS pin by default */
        GPIO_MuxConfig(spi0_cs3.pin, spi0_cs3.pin_mux, GPIO_PIN_TYPE_STD, GPIO_QUAL_ASYNC);
        GPIO_setDirectionMode(spi0_cs3.pin, GPIO_DIR_MODE_OUT);
        gpio_bit_set(spi0_cs3.pin_mux, spi0_cs3.pin);

        RT_TRY(rt_spi_bus_attach_device(&rt_spi0_dev3, "spi0_dev3", "spi0", (void*)&spi0_cs3));
    }

    /*----------------------------------------------------------------------------------------*/
    /* register SPI1 bus */
    RT_TRY(spi_bus_register(SPIB_BASE, &bus_spi1, "spi1"));

    /* attach spi_device_0 (SPI1 TF) to spi1 */
    {
        static struct rt_spi_device rt_spi1_dev0;
        static struct __spi_gpio_cs spi1_cs0 = __INIT_GPIO_CS(59);

        /* set CS pin by default */
        GPIO_MuxConfig(spi1_cs0.pin, spi1_cs0.pin_mux, GPIO_PIN_TYPE_STD, GPIO_QUAL_ASYNC);
        GPIO_setDirectionMode(spi1_cs0.pin, GPIO_DIR_MODE_OUT);
        gpio_bit_set(spi1_cs0.pin_mux, spi1_cs0.pin);

        RT_TRY(rt_spi_bus_attach_device(&rt_spi1_dev0, "spi1_dev0", "spi1", (void*)&spi1_cs0));
    }

    /*----------------------------------------------------------------------------------------*/
    /* register SPI2 bus */
    RT_TRY(spi_bus_register(SPIC_BASE, &bus_spi2, "spi2"));

    /* attach spi_device_1 (Nor Flash) to spi2 */
    {
        static struct rt_spi_device rt_spi2_dev0;
        static struct __spi_gpio_cs spi2_cs0 = __INIT_GPIO_CS(72);

        /* set CS pin by default */
        GPIO_MuxConfig(spi2_cs0.pin, spi2_cs0.pin_mux, GPIO_PIN_TYPE_STD, GPIO_QUAL_ASYNC);
        GPIO_setDirectionMode(spi2_cs0.pin, GPIO_DIR_MODE_OUT);
        gpio_bit_set(spi2_cs0.pin_mux, spi2_cs0.pin);

        RT_TRY(rt_spi_bus_attach_device(&rt_spi2_dev0, "spi2_dev0", "spi2", (void*)&spi2_cs0));
    }

    return RT_EOK;
}


